package org.JWCB.judge.service;


import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.Volume;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.netty.NettyDockerCmdExecFactory;
import org.JWCB.common.core.constants.JudgeConstants;
import org.JWCB.common.core.constants.UserConstants;
import org.JWCB.common.core.enums.CodeRunStatus;
import org.JWCB.judge.callback.DockerStartResultCallback;
import org.JWCB.judge.callback.StatisticsCallback;
import org.JWCB.judge.domain.vo.CompileResult;
import org.JWCB.judge.domain.vo.SandBoxExecuteRes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SandBoxService {
    @Value("${sandbox.docker.host:tcp://localhost:2375}")
    private String dockerHost;


    private String containerId;
    private DockerClient dockerClient;
    private static final String codeDir = System.getProperty("user.dir") + File.separator + UserConstants.CODE_DIR;

    private String userCodeDir;

    private String userCodeFile;

    @Value("${sandbox.limit.memory:100000000}")
    private  Long memoryLimit;

    @Value("${sandbox.limit.memory-swap:100000000}")
    private Long memorySwapLimit;

    @Value("${sandbox.limit.cpu:1}")
    private Long cpuLimit;

    @Value("${sandbox.limit.timeLimit:5}")
    private Long timeLimit;
    /**
     * 执行java代码
     */
    public SandBoxExecuteRes exeJavaCode(Long userId,String userCode, List<String> inputList) {
        createUserCodeFile(userId,userCode);
        initDockerSandBox();
        // 编译代码
        CompileResult compileResult = compileCodeByDocker();
        if(!compileResult.isCompiled()) {
            deleteUserCodeFile();
            deleteContainer();
            return SandBoxExecuteRes.fail(CodeRunStatus.COMPILE_FAILED,compileResult.getExeMessage());
        }
        return executeJavaCodeByDocker(inputList);
    }




    /**
     * 为用户提交代码创建文件
     */
    private void createUserCodeFile(Long userId,String userCode) {
        if(!FileUtil.exist(codeDir)) {
            FileUtil.mkdir(codeDir);
        }
        String time = LocalDateTimeUtil.format(LocalDateTime.now(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        userCodeDir = codeDir + File.separator + userId + "_" + time;
        userCodeFile = userCodeDir + File.separator + JudgeConstants.JAVA_CLASS_FILE_NAME;
        FileUtil.writeString(userCode,userCodeFile, "UTF-8");
    }



    /**
     * 创建docker沙箱
     */
    private void initDockerSandBox() {
        DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withDockerHost(dockerHost)
                .build();
        dockerClient = DockerClientBuilder
                .getInstance(clientConfig)
                .withDockerCmdExecFactory(new NettyDockerCmdExecFactory())
                .build();
        // 拉取java环境镜像
        pullJavaEnvImage();
        HostConfig hostConfig = getHostConfig();
        CreateContainerCmd createContainerCmd = dockerClient
                .createContainerCmd(JudgeConstants.JAVA_ENV_IMAGE)
                .withName(JudgeConstants.JAVA_CONTAINER_NAME);
        CreateContainerResponse createConfigResponse = createContainerCmd
                .withHostConfig(hostConfig)
                .withAttachStderr(true)
                .withAttachStderr(true)
                .withTty(true)
                .exec();
        containerId = createConfigResponse.getId();
        dockerClient.startContainerCmd(containerId).exec();
    }


    /**
     * 拉取java环境镜像
     */
    private void pullJavaEnvImage() {
        ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
        List<Image> imageList = listImagesCmd.exec();
        for(Image image : imageList) {
            String[] repoTags = image.getRepoTags();
            if(repoTags != null && repoTags.length != 0 && JudgeConstants.JAVA_ENV_IMAGE.equals(repoTags[0])) {
                return;
            }
        }
        PullImageCmd pullImageCmd = dockerClient.pullImageCmd(JudgeConstants.JAVA_ENV_IMAGE);
        try {
            pullImageCmd.exec(new PullImageResultCallback().awaitCompletion());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 限制资源
     */
    private HostConfig getHostConfig() {
        HostConfig hostConfig = new HostConfig();
        hostConfig.setBinds(new Bind(userCodeDir,new Volume(JudgeConstants.DOCKER_USER_CODE_DIR)));
        hostConfig.withMemory(memoryLimit);
        hostConfig.withMemorySwap(memorySwapLimit);
        hostConfig.withCpuCount(cpuLimit);
        hostConfig.withNetworkMode("none");
        hostConfig.withReadonlyRootfs(true);
        return hostConfig;
    }


    private CompileResult compileCodeByDocker() {
        String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVAC_CMD,null,containerId);
        DockerStartResultCallback dockerStartResultCallback = new DockerStartResultCallback();
        CompileResult compileResult = new CompileResult();
        try {
            dockerClient.execStartCmd(cmdId)
                    .exec(dockerStartResultCallback)
                    .awaitCompletion();
            if(CodeRunStatus.FAILED.equals(dockerStartResultCallback.getCodeRunStatus())) {
                compileResult.setCompiled(false);
                compileResult.setExeMessage(dockerStartResultCallback.getErrorMessage());
            }else {
                compileResult.setCompiled(true);
            }
            return compileResult;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    private String createExecCmd(String[] javaCmdArr,String inputArgs,String containerId) {
        if(!StrUtil.isEmpty(inputArgs)) {
            String[] inputArray = inputArgs.split(" ");// 入参
            javaCmdArr = ArrayUtil.append(JudgeConstants.DOCKER_JAVA_EXEC_CMD,inputArray);
        }

        ExecCreateCmdResponse createCmdResponse = dockerClient.execCreateCmd(containerId)
                .withCmd(javaCmdArr)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec();
        return createCmdResponse.getId();
    }



    private SandBoxExecuteRes executeJavaCodeByDocker(List<String> inputList) {
        List<String> outList = new ArrayList<>();
        long maxMemory = 0L;
        long maxUserTime = 0L;
        // 执行用户代码
        for(String inputArgs : inputList) {
            String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVA_EXEC_CMD,inputArgs,containerId);
            StopWatch stopWatch = new StopWatch();
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            StatisticsCallback statisticsCallback = statsCmd.exec(new StatisticsCallback());
            stopWatch.start();
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            try {
                dockerClient.execStartCmd(cmdId)
                        .exec(resultCallback)
                        .awaitCompletion(timeLimit, TimeUnit.SECONDS);
                if(CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                    return SandBoxExecuteRes.fail(CodeRunStatus.NOT_PASS_ALL_CASE);
                }
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stopWatch.stop();
            statsCmd.close();
            long userTime = stopWatch.getLastTaskTimeMillis();
            maxUserTime = Math.max(userTime,maxUserTime);
            Long memory = statisticsCallback.getMaxMemory();
            if(memory != null) {
                maxMemory = Math.max(maxMemory,statisticsCallback.getMaxMemory());
            }
            outList.add(resultCallback.getMessage().trim());
        }
        deleteContainer();;
        deleteUserCodeFile();
        
        return assembleSandBoxResult(inputList,outList,maxMemory,maxUserTime);
    }

    private SandBoxExecuteRes assembleSandBoxResult(List<String> inputList, List<String> outList, long maxMemory, long maxUserTime) {
        if(inputList.size() != outList.size()) {
            return SandBoxExecuteRes.fail(CodeRunStatus.NOT_PASS_ALL_CASE,outList,maxMemory,maxUserTime);
        }
        return SandBoxExecuteRes.success(CodeRunStatus.SUCCESS,outList,maxMemory,maxUserTime);
    }

    /**
     * 清理文件
     */
    private void deleteUserCodeFile() {
        FileUtil.del(userCodeDir);
    }

    /**
     * 清理docker容器
     */
    private void deleteContainer() {
        dockerClient.stopContainerCmd(containerId).exec();
        dockerClient.removeContainerCmd(containerId).exec();
        try {
            dockerClient.close();
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
